package com.ideaaedi.commonspring.register;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Consumer;

/**
 * spring 注册器
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 2022/6/28 13:31
 */
@Slf4j
public final class SpringRegister {
    
    private SpringRegister() {
        throw new UnsupportedOperationException("non-support create SpringRegister instance.");
    }
    
    /**
     * 注册spring bean
     *
     * @see SpringRegister#registerBean(ApplicationContext, Class, String, String, Consumer, Consumer)
     */
    public static String registerBean(@NonNull ApplicationContext applicationContext, @NonNull Class<?> clazz) {
        String beanName = StringUtils.uncapitalize(clazz.getSimpleName());
        beanName = StringUtils.uncapitalize(beanName);
        return registerBean(applicationContext, clazz, beanName, "singleton",
                null, null);
    }
    
    /**
     * 注册spring bean
     *
     * @see SpringRegister#registerBean(ApplicationContext, Class, String, String, Consumer, Consumer)
     */
    public static String registerBean(@NonNull ApplicationContext applicationContext, @NonNull Class<?> clazz,
                                      @Nullable Consumer<BeanDefinitionBuilder> beanDefinitionBuilderConsumer,
                                      @Nullable Consumer<BeanDefinition> beanDefinitionConsumer) {
        String beanName = StringUtils.uncapitalize(clazz.getSimpleName());
        beanName = StringUtils.uncapitalize(beanName);
        return registerBean(applicationContext, clazz, beanName, "singleton",
                beanDefinitionBuilderConsumer, beanDefinitionConsumer);
    }
    
    /**
     * 注册spring bean
     *
     * @see SpringRegister#registerBean(ApplicationContext, Class, String, String, Consumer, Consumer)
     */
    public static String registerBean(@NonNull ApplicationContext applicationContext, @NonNull Class<?> clazz, @NonNull String beanName,
                                      @Nullable Consumer<BeanDefinitionBuilder> beanDefinitionBuilderConsumer,
                                      @Nullable Consumer<BeanDefinition> beanDefinitionConsumer) {
        return registerBean(applicationContext, clazz, beanName, "singleton", beanDefinitionBuilderConsumer, beanDefinitionConsumer);
    }
    
    /**
     * 注册spring bean
     *
     * @see SpringRegister#registerBean(ApplicationContext, Class, String, String, Consumer, Consumer)
     */
    public static String registerBean(@NonNull ApplicationContext applicationContext, @NonNull Class<?> clazz, @NonNull String beanName) {
        return registerBean(applicationContext, clazz, beanName, "singleton", null, null);
    }
    
    /**
     * 注册spring bean
     *
     * @param applicationContext spring上下文
     * @param clazz 要注册的class
     * @param beanName bean name
     * @param scope 作用域
     * @param beanDefinitionBuilderConsumer BeanDefinitionBuilder自定义逻辑
     * @param beanDefinitionConsumer BeanDefinition自定义逻辑
     *
     * @return bean name
     */
    public static String registerBean(@NonNull ApplicationContext applicationContext, @NonNull Class<?> clazz,
                                      @NonNull String beanName, @Nullable String scope,
                                      @Nullable Consumer<BeanDefinitionBuilder> beanDefinitionBuilderConsumer,
                                      @Nullable Consumer<BeanDefinition> beanDefinitionConsumer) {
        Object bean = null;
        try {
            bean = applicationContext.getBean(beanName);
        } catch (Exception e) {
            // ignore
        }
        
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        if (beanDefinitionBuilderConsumer != null) {
            beanDefinitionBuilderConsumer.accept(beanDefinitionBuilder);
        }
        BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
        // 设置当前bean的作用域
        beanDefinition.setScope(scope);
        if (beanDefinitionConsumer != null) {
            beanDefinitionConsumer.accept(beanDefinition);
        }
        // 将applicationContext转换为ConfigurableApplicationContext
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        // 获取bean工厂并转换为DefaultListableBeanFactory
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
        if (bean != null) {
            log.warn("A bean with name '" + beanName + "' has already been defined. It is '" + bean + "'. We try to "
                    + "overriding it with bean '" + beanDefinition + "'.");
        }
        defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinition);
        log.debug("register bean '" + beanName + "' success.");
        return beanName;
    }
    
    /**
     * 将指定bean解析controller bean
     * <br>
     * 核心是利用{@link AbstractHandlerMethodMapping#detectHandlerMethods(Object)}，将spring-bean解析为controller
     *
     * @param applicationContext
     *         spring上下文
     * @param beanName
     *         要解析的spring bean
     */
    public static void registerController(@NonNull ApplicationContext applicationContext, @NonNull String beanName) {
        // step1. 检查beanName存在
        Object bean = null;
        try {
            bean = applicationContext.getBean(beanName);
        } catch (Exception e) {
            // ignore
        }
        if (bean == null) {
            throw new RuntimeException("non-exist spring bean whose name  is [" + beanName + "]. Please register it "
                    + "first.");
        }
        
        // step2. 将beanName解析为controller
        try {
            RequestMappingHandlerMapping requestMappingHandlerMapping =
                    applicationContext.getBean(RequestMappingHandlerMapping.class);
            Method method =
                    requestMappingHandlerMapping.getClass().getSuperclass().getSuperclass().getDeclaredMethod(
                            "detectHandlerMethods", Object.class);
            boolean accessible = method.canAccess(requestMappingHandlerMapping);
            method.setAccessible(true);
            method.invoke(requestMappingHandlerMapping, beanName);
            method.setAccessible(accessible);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException("registerController occur exception.", e);
        }
        log.debug("register controller-bean '" + beanName + "' success.");
    }
}
